#include "inputmanager_winpc.h"
#include "util/assert.h"
#define INPUT_DEADZONE  ( 0.24f * FLOAT(0x7FFF) )

namespace gecko
{

	XboxController::XboxController()
		: m_playerID(0)
	{
		memset(&m_padState, 0, sizeof(XINPUT_STATE));
		memset(&m_padPreviousState, 0, sizeof(XINPUT_STATE));
	}

	IInputManager * CreateInputManager()
	{
		return new InputManager_WinPC;
	}

	extern HWND g_hwnd;
	extern HINSTANCE g_hinstance;

	InputManager_WinPC::InputManager_WinPC()
		: IInputManager()
		, m_directInput(NULL)
		, m_keyboardDevice(NULL)
	{
		// clear out previous key buffer
		memset(m_previousKeyBuffer, 0, sizeof(char) * MAX_KEYS);
	}

	void InputManager_WinPC::InitialiseDevices()
	{
		// initialise XInput for gamepads
		XInputEnable(true);

		// initialise DirectInput

		HRESULT hr; 

		hr = DirectInput8Create(g_hinstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, NULL); 
		if (FAILED(hr))
		{ 
			// DirectInput not available, take action 
		} 

		// initialise keyboard
		
		hr = m_directInput->CreateDevice(GUID_SysKeyboard, &m_keyboardDevice, NULL); 
		if (FAILED(hr))
		{ 
			// keyboard not initialised, take action
		} 

		// set the keyboard data format

		hr = m_keyboardDevice->SetDataFormat(&c_dfDIKeyboard); 

		if (FAILED(hr))
		{ 
			// data format failed, take action
		} 

		// Set the cooperative level 

		hr = m_keyboardDevice->SetCooperativeLevel(g_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); 

		if (FAILED(hr))
		{ 
			// cooperation level failed, take action
		} 

		// acquire the keyboard device
		if (m_keyboardDevice)
		{
			m_keyboardDevice->Acquire(); 
		}
	}

	void InputManager_WinPC::DestroyDevices()
	{
		// destroy XInput
		XInputEnable(false);

		// release keyboard
		if (m_keyboardDevice)
		{
			m_keyboardDevice->Unacquire();
			m_keyboardDevice->Release();
			m_keyboardDevice = NULL;
		}

		// shutdown DirectInput
		if (m_directInput)
		{
			m_directInput->Release();
			m_directInput = NULL;
		}
	}

	void InputManager_WinPC::PollXInputGamepads()
	{
		DWORD dwResult;
		
		for (int i = 0; i < MAX_PADS; i++)
		{
			// backup previous pad state
			m_pads[i].m_padPreviousState = m_pads[i].m_padState;

			// Simply get the state of the controller from XInput.
			dwResult = XInputGetState(i, &m_pads[i].m_padState);

			if( dwResult == ERROR_SUCCESS )
			{
				// Zero value if thumbsticks are within the dead zone 
				float LthumbLeft	= (m_pads[i].m_padState.Gamepad.sThumbLX > -INPUT_DEADZONE) ? 0.0f : -(float)m_pads[i].m_padState.Gamepad.sThumbLX / 32767.5f;
				float LthumbRight	= (m_pads[i].m_padState.Gamepad.sThumbLX < INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbLX / 32767.5f;
				float LthumbUp		= (m_pads[i].m_padState.Gamepad.sThumbLY < INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbLY / 32767.5f;
				float LthumbDown	= (m_pads[i].m_padState.Gamepad.sThumbLY > -INPUT_DEADZONE) ? 0.0f : -(float)m_pads[i].m_padState.Gamepad.sThumbLY / 32767.5f;

				float RthumbLeft	= (m_pads[i].m_padState.Gamepad.sThumbRX > -INPUT_DEADZONE) ? 0.0f : -(float)m_pads[i].m_padState.Gamepad.sThumbRX / 32767.5f;
				float RthumbRight	= (m_pads[i].m_padState.Gamepad.sThumbRX < INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbRX / 32767.5f;
				float RthumbUp		= (m_pads[i].m_padState.Gamepad.sThumbRY < INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbRY / 32767.5f;
				float RthumbDown	= (m_pads[i].m_padState.Gamepad.sThumbRY > -INPUT_DEADZONE) ? 0.0f : -(float)m_pads[i].m_padState.Gamepad.sThumbRY / 32767.5f;

				float thumbLX = (m_pads[i].m_padState.Gamepad.sThumbLX < INPUT_DEADZONE && m_pads[i].m_padState.Gamepad.sThumbLX > -INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbLX / 32767.5f;
				float thumbLY = (m_pads[i].m_padState.Gamepad.sThumbLY < INPUT_DEADZONE && m_pads[i].m_padState.Gamepad.sThumbLY > -INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbLY / 32767.5f;
				float thumbRX = (m_pads[i].m_padState.Gamepad.sThumbRX < INPUT_DEADZONE && m_pads[i].m_padState.Gamepad.sThumbRX > -INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbRX / 32767.5f;
				float thumbRY = (m_pads[i].m_padState.Gamepad.sThumbRY < INPUT_DEADZONE && m_pads[i].m_padState.Gamepad.sThumbRY > -INPUT_DEADZONE) ? 0.0f : (float)m_pads[i].m_padState.Gamepad.sThumbRY / 32767.5f;
				float leftTrigger = (float)m_pads[i].m_padState.Gamepad.bLeftTrigger / 255.0f;
				float rightTrigger = (float)m_pads[i].m_padState.Gamepad.bRightTrigger / 255.0f;

				// axis events
				QueuePadAxisEvent(i, thumbLX, thumbLY, Evt_PadLThumbMove);
				QueuePadAxisEvent(i, thumbRX, thumbRY, Evt_PadRThumbMove);
				QueuePadAxisEvent(i, LthumbLeft, 0.0f, Evt_PadLThumbLeft);
				QueuePadAxisEvent(i, LthumbRight, 0.0f, Evt_PadLThumbRight);
				QueuePadAxisEvent(i, LthumbUp, 0.0f, Evt_PadLThumbUp);
				QueuePadAxisEvent(i, LthumbDown, 0.0f, Evt_PadLThumbDown);
				QueuePadAxisEvent(i, RthumbLeft, 0.0f, Evt_PadRThumbLeft);
				QueuePadAxisEvent(i, RthumbRight, 0.0f, Evt_PadRThumbRight);
				QueuePadAxisEvent(i, RthumbUp, 0.0f, Evt_PadRThumbUp);
				QueuePadAxisEvent(i, RthumbDown, 0.0f, Evt_PadRThumbDown);
				QueuePadAxisEvent(i, leftTrigger, 0.0f, Evt_PadLeftTrigger);
				QueuePadAxisEvent(i, rightTrigger, 0.0f, Evt_PadRightTrigger);

				// do button events here...
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_A, Evt_PadA);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_B, Evt_PadB);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_X, Evt_PadX);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_Y, Evt_PadY);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_DPAD_UP, Evt_PadDUp);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_DPAD_DOWN, Evt_PadDDown);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_DPAD_LEFT, Evt_PadDLeft);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_DPAD_RIGHT, Evt_PadDRight);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_START, Evt_PadStart);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_BACK, Evt_PadBack);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_LEFT_THUMB, Evt_PadLeftThumb);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_RIGHT_THUMB, Evt_PadRightThumb);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_LEFT_SHOULDER, Evt_PadLShoulder);
				QueuePadButtonEvents(i, XINPUT_GAMEPAD_RIGHT_SHOULDER, Evt_PadRShoulder);
			}
		}
	}

	int DirectInputKeycodeToEvent(int diKey)
	{
		switch (diKey)
		{
		case DIK_UPARROW:
			return IInputManager::Evt_UpArrow;

		case DIK_DOWNARROW:
			return IInputManager::Evt_DownArrow;

		case DIK_LEFTARROW:
			return IInputManager::Evt_LeftArrow;

		case DIK_RIGHTARROW:
			return IInputManager::Evt_RightArrow;

		case DIK_Q:
			return IInputManager::Evt_Q;

		case DIK_A:
			return IInputManager::Evt_A;

		default:
			return 0;
		}
	}

	void InputManager_WinPC::PollDirectInputKeyboard()
	{
		char keyBuffer[MAX_KEYS];
		HRESULT hr; 

		// clear key buffer
		memset(keyBuffer, 0, sizeof(char) * MAX_KEYS);

		// get the state of all keys
		hr = m_keyboardDevice->GetDeviceState(sizeof(keyBuffer),(LPVOID)&keyBuffer); 
		
		if (FAILED(hr))
		{ 
			if (hr == DIERR_INPUTLOST)
			{
				// reacquire keyboard
				hr = m_keyboardDevice->Acquire();

				if (FAILED(hr))
				{
					DebugPrint("Acquire failed");
					return;
				}
			}
			else
			{
				return;
			}
		} 

		// turn any keys into input events
		for (int i = 0; i < MAX_KEYS; i++)
		{
			if (keyBuffer[i] & 0x80)	// key down
			{
				InputEvent e;
				e.flags = 0;
				e.deviceID = 0;
				e.playerID = 0;
				e.value1 = 1.0f;
				e.value2 = 0.0f;
				e.inputID = DirectInputKeycodeToEvent(i);
				e.flags = InputEvent::Flags_ButtonDown;
				QueueEvent(e);
			}

			if ((keyBuffer[i] & 0x80) && !(m_previousKeyBuffer[i] & 0x80)) // key pressed
			{
				InputEvent e;
				e.flags = 0;
				e.deviceID = 0;
				e.playerID = 0;
				e.value1 = 1.0f;
				e.value2 = 0.0f;
				e.inputID = DirectInputKeycodeToEvent(i);
				e.flags = InputEvent::Flags_ButtonPressed;
				QueueEvent(e);
			}

			if (!(keyBuffer[i] && 0x80) && (m_previousKeyBuffer[i] & 0x80)) // key released
			{
				InputEvent e;
				e.flags = 0;
				e.deviceID = 0;
				e.playerID = 0;
				e.value1 = 0.0f;
				e.value2 = 0.0f;
				e.inputID = DirectInputKeycodeToEvent(i);
				e.flags = InputEvent::Flags_ButtonReleased;
				QueueEvent(e);
			}
		}

		// copy buffer to previous
		memcpy(m_previousKeyBuffer, keyBuffer, sizeof(char) * MAX_KEYS);
	}

	void InputManager_WinPC::PollDevices()
	{
		PollXInputGamepads();
		PollDirectInputKeyboard();
	}

	void InputManager_WinPC::AcquireDevices()
	{
		if (m_keyboardDevice)
		{
			HRESULT hr;

			hr = m_keyboardDevice->SetCooperativeLevel(g_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); 

			if (FAILED(hr))
			{ 
				DebugPrint("Cooperation level failed, take action");
			} 

			// acquire keyboard
			hr = m_keyboardDevice->Acquire();

			if (FAILED(hr))
			{
				DebugPrint("Acquire failed");
			}
		}
	}

	void InputManager_WinPC::UnacquireDevices()
	{
		if (m_keyboardDevice)
		{
			// unacquire keyboard
			HRESULT hr = m_keyboardDevice->Unacquire();

			if (FAILED(hr))
			{
				DebugPrint("Un-Acquire failed");
			}
		}
	}

	void InputManager_WinPC::QueuePadButtonEvents(int padID, int buttonID, int eventID)
	{
		InputEvent e;
		e.flags = 0;
		e.value2 = 0.0f;
		e.deviceID = padID;
		e.playerID = m_pads[padID].m_playerID;

		// pressed event
		if ((m_pads[padID].m_padState.Gamepad.wButtons & buttonID) && !(m_pads[padID].m_padPreviousState.Gamepad.wButtons & buttonID))
		{
			e.value1 = 1.0f;
			e.inputID = eventID;
			e.flags = InputEvent::Flags_ButtonPressed;
			QueueEvent(e);
			return;
		}

		// released event
		if ((m_pads[padID].m_padPreviousState.Gamepad.wButtons & buttonID) && !(m_pads[padID].m_padState.Gamepad.wButtons & buttonID))
		{
			e.value1 = 0.0f;
			e.inputID = eventID;
			e.flags = InputEvent::Flags_ButtonReleased;
			QueueEvent(e);
			return;
		}

		// down event
		if (m_pads[padID].m_padState.Gamepad.wButtons & buttonID)
		{
			e.value1 = 1.0f;
			e.inputID = eventID;
			e.flags = InputEvent::Flags_ButtonDown;
			QueueEvent(e);
			return;
		}
	}

	void InputManager_WinPC::QueuePadAxisEvent(int padID, float axisValue1, float axisValue2, int eventID)
	{
		InputEvent e;
		e.flags = 0;
		e.deviceID = padID;
		e.playerID = m_pads[padID].m_playerID;

		if (axisValue1 != 0.0f || axisValue2 != 0.0f)
		{
			e.value1 = axisValue1;
			e.value2 = axisValue2;
			e.inputID = eventID;
			QueueEvent(e);
		}
	}
}